This month we will look at generating the content of HTML pages from JavaScript.
This is not the same as Dynamic HTML, which is not handled by any of the current
Amiga browsers. Dynamic HTML allows JavaScript to change the content and
appearance of a page after it has been rendered by the browser. We will only
consider the content of the page before the browser shows it, that is, at the
time of writing the page to the browser window. We used document.write() to add a
date to a page in Part One, but it can do a lot more than that.

**Getting information about the browser

PNG is a superior format to GIF for web images, with one exception, not all
browsers support it. The situation has improved, the latest versions of Netscape
and Internet Explorer will display PNG but many people use older versions of
software and are unable to upgrade. JavaScript provides a way to
determine the browser being used, so we can create a page that uses the best
images for a particular browser. The JavaScript object containing information
about the browser is navigator (since JavaScript was originally written for the
Netscape Navigator browser. Add this script to a page to show information on the
browser displaying it

<script type="text/javascript" language="JavaScript">
<!--
NavList = 'appCodeName appName appVersion userAgent';
NavArray = NavList.split(' ');

document.write('<h3>Properties of the Navigator object</h3>');
document.write('<table>');
for ( i = 0; i < NavArray.length; i++)
{
document.write('<tr><td><b>' + NavArray[i] + '</b></td><td>');
eval('document.write(navigator.' + NavArray[i] + ');');
document.writeln('</td></tr>');
}
document.write('</table>');
// -->
</script>

The properties of the navigator object are:

appCodeName The code name of the browser
appName The name of the browser
appVersion The browser's version string
userAgent The User-Agent string as sent by the browser in the headers of page requests

We could have simply listed these with separate commands of the form
document.write(navigator.appName), but this way shows some more elements
of JavaScript and comes in useful at the next stage. The first line of
the script creates a string containing all the properties we want to
cover, separated by spaces. The string object has a method called split.
This splits the string into substrings and returns them as an array. It
takes a single argument, the character on which to split the string, a
space in this case. Now we can use the for loop (as discussed in Part
Two) to perform the same operation on each element of the array. The
initial document.write statements just set up an HTML table so the
information is displayed clearly. The first line of the loop displays
the property's name using a straightforward document.write, but showing
its value is a little trickier. The eval() method evaluates its argument
as a string and executes it as a JavaScript command (ARexx programmers
will recognise this as being the same as the Interpret statement). The
first time around the loop, NavArray[i] contains "appCodeName", so eval
executes "document.write(navigator.appCodeName);".

The navigator object has two more properties, mimeTypes and plugins. These are
arrays containing lists of the MIME types and plugins available to the browser.
We can list these too by adding two more loops to the script, after the </table>
line.

document.writeln('<p><b>Plugins</b><p><ul>');
for (i = 0; i < navigator.plugins.length; i++) document.writeln('<li>' + navigator.plugins[i].name);
document.writeln('</ul><p>');

document.writeln('<p><b>Mime Types</b><p><ul>');
for (i = 0; i < navigator.mimeTypes.length; i++) document.writeln('<li>' + navigator.mimeTypes[i].type);
document.writeln('</ul><p>');

Here the use of arrays makes life much simpler, we simply loop through each array
printing out the relevant value. In the case of the plugins array, this is the
name property of each element. For mimeTypes it's the type property. It would be
nicer if there was consistent naming here, but life's not supposed to be that
simple.




**Browser-dependent image types

Now that we can tell what the user's browser is, and can do, let's use this
information in a page. Identifying whether a browser can use PNG images isn't as
simple as checking the plugins list, since PNG decoding can be handled internally
rather than by a plugin (or by datatypes in the case of Amiga browsers). Instead,
we'll check the User Agent string for indications of a browser that supports PNG.
There are two ways of handling multiple image types. The first is to have two
copies of each page, say mypage.html with GIF images and mypage_png.html with PNG
images. In this case, we put the browser check at the top of mypage.html, forcing
the browser to load a different page if we recognise it as being able to handle
PNG images. JavaScript is executed as it is loaded, so we put this near the top
of the page, in the <HEAD> section. Executing it as on onLoad event would mean
that it wouldn't be run until the whole page and loaded, and many of the GIF
images had started loading, making the saving of using the smaller PNG images
pointless. This is the script to use in the <HEAD> of the document.

<script type="text/javascript" language="JavaScript">
<!--
TempA = location.href.split('.htm');
TempA[0] = TempA[0] + '_png';
NewURL = TempA.join('.htm');

if (navigator.userAgent.indexOf('Amiga') > -1) location.replace(NewURL);
// -->
</script>

The first three lines generate the URL of the PNG version of the page. This means
we can use the same function in every page, without needing to change URLs. The
first line takes the URL of the current document, given by location.href, a
property of the document, and splits it at the file extension. The next line adds
the "_png" extension to the page name and the third replaces the rest of the URL
using the join() method of the array. This is the counterpart of split(), it
joins the array elements as a single string, using the argument as a separator.

The final line checks to see if the User Agent string contains the word
Amiga, all JavaScript-capable Amiga browsers can handle PNG images, and
uses location.replace() to load the new page in the place of the
original. All of our browser include the word "Amiga" in the userAgent
string, even when spoofing as Netscape or Internet Explorer. This is the
most reliable way to test for an Amiga browser, even an undercover one.
It's not only Amiga browsers that can handle PNG, so we'll add some more
browser checks.

if (navigator.userAgent.indexOf('Mozilla/4') == 0) location.replace(NewURL);

checks for version four of Netscape, which supports PNG. Then we check for
version five of any browser with these lines

Version = navigator.appVersion.split(' ');
if (Version[0] >= 5) location.replace(NewURL);

The appVersion property contains the version number followed by extra
information. We use split to isolate the version number so that we may compare it
with a number.

Since this is a general function, with no reference to the calling page's URL, we
can speed up page loading slightly by moving it out of the page and into a
separate file. Save it as a separate file, called LoadPNG.js and replace the
function in the <HEAD> section of each page with

<script type="text/javascript" language="JavaScript" src="LoadPNG.js"></script>

The SRC attribute tells the browser to fetch the external file and execute it as
if it were within the <SCRIPT> tag. Any code withing the tag will be ignored,
unless the browser doesn't recognise the SRC attribute (as with old versions of
Netscape). The first page will take slightly longer to load, since the server
will have to fetch the external file. But then the file will be in your browser's
cache so loading will be faster.


**Another way

The other way of handling this is to have only one page and use document.write to
generate the actual <IMG...> tag, using either GIF or PNG images as appropriate.
This results in a larger document. Each image needs a call to the function that
decides what to include, and you need to include the normal <IMG> information in
a <NOSCRIPT> tag for browsers that don't support JavaScript. It has the advantage
that it only requires one copy of each page, but is probably only worth using if
there are several other choices to be made within the page, such as whether to
use Java, Shockwave, RealAudio etc. This is how it would work. Each
image is called with

<script type="text/javascript"><!--
ShowImage('images/image1',320,80,'An image'); // -->
</script>
<noscript><img src="images/image1.gif" width="312" height="69" alt="An image"></noscript>

The first three lines load the image via JavaScript, the fourth is for
non-JavaScript browsers. The ShowImage function is as follows

<script type="text/javascript" language="JavaScript">
<!--
if (navigator.userAgent.indexOf('Amiga') > -1)
{
ImgExt = '.png';
}
{
if (navigator.userAgent.indexOf('Mozilla/4') == 0)
{
ImgExt = '.png';
}
{
Version = navigator.appVersion.split(' ');
if (Version[0] >= 5)
{
ImgExt = '.png';
}
{
ImgExt = '.gif';
}
}
}

function ShowImage(ImgName,ImgWidth,ImgHeight,ImgAlt,ImgAttr)
{
document.write('<IMG SRC="' + ImgName + ImgExt + '" + WIDTH="' + ImgWidth + '" HEIGHT="' + ImgHeight + '" ALT="' + ImgAlt + '"');
if (ImgAttr != null) document.write(' ' + ImgAttr);
document.write('>');
}
// -->
</script>

We only need to determine the browser type once, so we deal with that as the page loads.
The nested if statements look complex, but that's only because there's no else keyword in
JavaScript, it's implicit. the contents of the second set of braces are executed if the if
condition is untrue.


if (condition)
{do this if true}
{else do this}

The braces are not required if there is only a single statement
for each of true and false, but their use is strongly recommended when
using nested if/else tests, otherwise it's not always clear which if
an else clause can refer to.

The function itself only writes the <IMG> tag, using the image extension
appropriate to the browser. The ImgAttr argument is for things like
ALIGN and BORDER attributes and is only written if the argument is
defined. If the (ImgAttr != null) test were omitted, JavaScript would
write "undefined" to the browser window whenever no value was given.

This is a complex way of dealing with multiple image formats, but is of
more use when using other data formats. For example, you could put in a
link to multimedia content only if the browser has the relevant plugin
installed. Later on, we will look at using cookies to store user
preferences, then this method can be used to generate customised pages,
all without any need for server side CGI scripts.


**Netscape.png: Even with the large range of plugins reported by Netscape, there's no mention of PNG. That's why we need to check the version number.